import fs from 'fs/promises';
import path from 'path';
import zlib from 'zlib';
import { promisify } from 'util';
import { ApiError } from '../utils/ApiError';
import { StatusCodes } from 'http-status-codes';

const gzipAsync = promisify<Buffer, Buffer>(zlib.gzip);

export interface LogRotationConfig {
  logsDir: string;
  archiveDir: string;
  archiveRetention?: number; // days
  cleanupInterval?: number;  // hours
  enableCompression?: boolean;
}

export class LogRotationService {
  private config: Required<LogRotationConfig>;
  private cleanupInterval?: NodeJS.Timeout;
  private isProcessing: boolean = false;

  constructor(config: LogRotationConfig) {
    this.config = {
      archiveRetention: 7,
      cleanupInterval: 24,
      enableCompression: true,
      ...config
    };
  }

  async start(): Promise<void> {
    try {
      await this.ensureDirectories();
      if (this.config.cleanupInterval > 0) {
        this.startCleanupSchedule();
      }
    } catch (error) {
      throw new ApiError(
        StatusCodes.INTERNAL_SERVER_ERROR,
        'Failed to create required directories',
        { error: error instanceof Error ? error.message : String(error) }
      );
    }
  }

  stop(): void {
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
      this.cleanupInterval = undefined;
    }
  }

  private async ensureDirectories(): Promise<void> {
    await Promise.all([
      fs.mkdir(this.config.logsDir, { recursive: true }),
      fs.mkdir(this.config.archiveDir, { recursive: true })
    ]);
  }

  private startCleanupSchedule(): void {
    const interval = this.config.cleanupInterval * 60 * 60 * 1000;
    this.cleanupInterval = setInterval(() => {
      if (!this.isProcessing) {
        this.rotateNow().catch(console.error);
      }
    }, interval);

    if (this.cleanupInterval.unref) {
      this.cleanupInterval.unref();
    }
  }

  async rotateNow(): Promise<void> {
    if (this.isProcessing) {
      return;
    }

    this.isProcessing = true;
    try {
      await this.processLogFiles();
      await this.cleanupOldArchives();
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      throw new ApiError(
        StatusCodes.INTERNAL_SERVER_ERROR,
        'Failed to perform log rotation',
        { error: message }
      );
    } finally {
      this.isProcessing = false;
    }
  }

  private async processLogFiles(): Promise<void> {
    const entries = await fs.readdir(this.config.logsDir, { withFileTypes: true });
    const logFiles = entries
      .filter(entry => entry.isFile() && entry.name.endsWith('.log'))
      .map(entry => entry.name);

    const errors: Error[] = [];
    for (const file of logFiles) {
      try {
        await this.processLogFile(file);
      } catch (error) {
        const originalError = error instanceof Error ? error : new Error(String(error));
        errors.push(originalError);
        throw originalError; // Re-throw to propagate the original error
      }
    }
  }

  private async processLogFile(filename: string): Promise<void> {
    const filePath = path.join(this.config.logsDir, filename);
    const stats = await fs.stat(filePath);
    const fileAge = this.getFileAge(stats.mtime);

    if (fileAge > 1) {
      const content = await fs.readFile(filePath);
      const archiveName = this.getArchiveName(filename, stats.mtime);
      const archivePath = path.join(this.config.archiveDir, archiveName);

      if (this.config.enableCompression) {
        const compressed = await gzipAsync(content);
        await fs.writeFile(archivePath, compressed);
      } else {
        await fs.copyFile(filePath, archivePath.replace('.gz', ''));
      }
      await fs.unlink(filePath);
    }
  }

  private getArchiveName(filename: string, date: Date): string {
    const timestamp = date.toISOString().replace(/:/g, '-');
    return `${filename}.${timestamp}${this.config.enableCompression ? '.gz' : ''}`;
  }

  private getFileAge(mtime: Date): number {
    return (Date.now() - mtime.getTime()) / (1000 * 60 * 60 * 24);
  }

  private async cleanupOldArchives(): Promise<void> {
    const entries = await fs.readdir(this.config.archiveDir, { withFileTypes: true });
    const archiveFiles = entries
      .filter(entry => entry.isFile() && (
        entry.name.endsWith('.gz') || entry.name.endsWith('.log')
      ))
      .map(entry => entry.name);

    const errors: Error[] = [];
    for (const file of archiveFiles) {
      try {
        const filePath = path.join(this.config.archiveDir, file);
        const stats = await fs.stat(filePath);
        const fileAge = this.getFileAge(stats.mtime);

        if (fileAge > this.config.archiveRetention) {
          await fs.unlink(filePath);
        }
      } catch (error) {
        errors.push(error instanceof Error ? error : new Error(String(error)));
      }
    }

    if (errors.length > 0) {
      throw new ApiError(
        StatusCodes.INTERNAL_SERVER_ERROR,
        'Failed to cleanup archives',
        { error: errors.map(e => e.message).join('; ') }
      );
    }
  }
}
